package models;

import java.util.Calendar;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.persistence.*;

import controllers.Events;
import controllers.Notes;
import controllers.UserStories;

import play.db.jpa.Model;
import play.modules.search.Field;
import play.modules.search.Indexed;

@Indexed
@Entity
@Table(name = "Projects")
public class Project extends Model {

	@Field
	public String name;

	@Column(name = "default_sprint_duration")
	public int defaultSprintDuration; // duration is in days

	public boolean status;

	@OneToMany(mappedBy = "project", cascade = { CascadeType.REMOVE })
	public List<EffortUnitProject> projectUnits = new LinkedList<EffortUnitProject>();

	// Method should be created and called from the constructor to
	// put the current DATETIME into the field.
	@Column(name = "creation_date")
	public Calendar creationDate;

	@Field
	@Lob
	public String description;

	@OneToMany(mappedBy = "project")
	public List<Event> events = new LinkedList<Event>();

	@OneToMany(mappedBy = "project")
	public List<Component> components = new LinkedList<Component>();

	// @OneToMany(mappedBy = "project")
	// public List<Log> logs=new LinkedList<Log>();

	@OneToMany(mappedBy = "project", cascade = { CascadeType.REMOVE })
	public List<UserRoleProject> user_project_roles = new LinkedList<UserRoleProject>();

	@OneToMany(mappedBy = "project")
	public List<ApplicationRole> appRoles = new LinkedList<ApplicationRole>();

	@OneToMany(mappedBy = "project")
	public List<TaskStatus> taskStatuses = new LinkedList<TaskStatus>();

	@OneToMany(mappedBy = "project")
	public List<TaskType> taskTypes = new LinkedList<TaskType>();

	/**
	 * Creates a project with the specified <code>name </code>and
	 * <code>description </code>, It makes <code>user </code> the admin of this
	 * project, however this will not be in effect until the system admin
	 * approves. The <code>status </code> filed in this project will be set
	 * initially to false, to represent that this project creation is not
	 * confirmed by the system admin yet
	 * 
	 * @param name
	 *            the name of this project
	 * @param description
	 *            the description of this project
	 * @param user
	 *            the user that requested to create the project
	 * @param defaultSprintDuration
	 *            sprint duration
	 * @param status
	 *            the status of the project
	 * @param creationDate
	 *            the date of creation
	 */
	public Project(String name, int defaultSprintDuration, boolean status,
			String description) {
		super();
		this.name = name;
		this.defaultSprintDuration = defaultSprintDuration;
		this.status = status;
		this.creationDate = Calendar.getInstance();
		// setDefaultEffortUnit();
		this.description = description;
	}

	public Project(String name, String description, boolean status) {
		super();
		this.name = name;
		this.status = status;
		this.creationDate = Calendar.getInstance();
		this.description = description;
		// setDefaultEffortUnit();
	}

	/*
	 * public void setDefaultEffortUnit() { EffortUnit hours =
	 * EffortUnit.find("byUnit", "Hours").first(); new EffortUnitProject(true,
	 * hours, this).save();
	 * 
	 * }
	 */

	/**
	 * Overides built in delete method to delete everything thats related to a
	 * project. Used when deleting a project.
	 * 
	 * @author Adam El Shafei
	 */

	public Project delete() {
		deleteTasks();
		deleteUserStories();
		deleteNotes();
		deleteEvents();
		deleteComponentes();
		deleteApplicationRoles();
		deleteTaskTypes();
		deleteTasksStatus();

		return super.delete();
	}

	private void deleteTasksStatus() {
		for (TaskStatus stat : taskStatuses)
			stat.delete();

	}

	private void deleteTaskTypes() {
		for (TaskType type : taskTypes)
			type.delete();

	}

	private void deleteApplicationRoles() {
		for (ApplicationRole app : appRoles)
			app.delete();
	}

	public void deleteTasks() {
		// Delete tasks in events that are in sprints in this project
		List<Task> eventsTasks = Task
				.find(
						"select t from Task t , Event e1, Event e2 where e1.sprint=e2 and e2.project=? and t member of  e1.tasks",
						this).fetch();
		// Delete tasks that are in sprints in this project
		List<Task> sprintsTasks = Task
				.find(
						"select t from Task t , Event e where e.project=? and t member of  e.tasks",
						this).fetch();
		// Filter duplicate tasks
		Set<Task> allTasks = new HashSet<Task>();
		allTasks.addAll(eventsTasks);
		allTasks.addAll(sprintsTasks);

		for (Task task : allTasks)
			task.delete();
	}

	public void deleteUserStories() {
		UserStories.deleteProjectUserStories((Long) getEntityId());
	}

	public void deleteEvents() {
		for (Event event : events)
			event.delete();
	}

	public void deleteComponentes() {
		for (Component comp : components)
			comp.delete();
	}

	public void deleteNotes() {
		Notes.deleteNote((Long) getEntityId());
	}

	/**
	 * Given a project id, this method returns all tasks related to this
	 * project.
	 * 
	 * @param projectId
	 * @return List<Task>
	 */
	public List<Task> getAllProjectTasks() {
		List<Task> tasksList = new LinkedList<Task>();

		List<Event> projectEvents = this.events;

		for (int i = 0; i < projectEvents.size(); i++)
			for (int j = 0; j < projectEvents.get(i).tasks.size(); j++)
				tasksList.add(projectEvents.get(i).tasks.get(j));

		// Add tasks that are in events in sprints of the project
		for (Event sprint : projectEvents)
			for (Event event : sprint.events)
				tasksList.addAll(event.tasks);

		Set<Task> projectTasks = new HashSet<Task>();
		projectTasks.addAll(tasksList);

		tasksList.clear();
		tasksList.addAll(projectTasks);
		return tasksList;

	}

	/**
	 * A method that gets the list of all tasks of a certain type within the
	 * project.
	 * 
	 * @param type
	 *            The type that will be selected.
	 * @param projectId
	 *            The id of the project.
	 * @return List<Task>
	 */
	public List<Task> getTasksOfType(TaskType type) {
		List<Task> tasks = new LinkedList<Task>();
		List<Task> projectTasks = this.getAllProjectTasks();
		for (int i = 0; i < projectTasks.size(); i++) {
			if (projectTasks.get(i).type.type.equals(type.type)) {
				tasks.add(projectTasks.get(i));
			}
		}
		return tasks;
	}

	/**
	 * This method returns a list of all Task Statuses available for a project.
	 * It returns all System Task Statuses as well as the statuses that were
	 * created from within a specific project
	 * 
	 * @author Rana Ashraf
	 * @param projectId
	 * @return List<TaskStatus>
	 */
	public List<TaskStatus> availableTaskStatuses() {
		List<TaskStatus> available = new LinkedList<TaskStatus>();
		available = TaskStatus.find("isSystem", true).fetch();
		if (this != null)
			for (int i = 0; i < this.taskStatuses.size(); i++) {
				available.add(this.taskStatuses.get(i));
			}
		return available;

	}

	/**
	 * This method returns a list of all TaskTypes available for a project. It
	 * returns all System Task types as well as the types that were created from
	 * within a specific project
	 * 
	 * @param projectId
	 * @return List<TaskType>
	 */
	public List<TaskType> availableTaskTypes() {
		List<TaskType> available = new LinkedList<TaskType>();
		available = TaskType.find("isSystem", true).fetch();
		if (this != null)
			for (int i = 0; i < this.taskTypes.size(); i++) {
				available.add(this.taskTypes.get(i));
			}
		return available;

	}

	/**
	 * returns a list of pending task review requests of a specific project.
	 * 
	 * @author Rana Ashraf
	 * @param projectId
	 * @return List<TaskUser>
	 */
	public List<TaskUser> getPendingTaskReviewRequests() {

		List<TaskUser> allPendingReviews = TaskUser.find(
				"select tu from TaskUser tu where tu.status=?", false).fetch();
		LinkedList<TaskUser> projectPendingReviews = new LinkedList<TaskUser>();
		List<Task> projectTasks = this.getAllProjectTasks();
		for (int i = 0; i < allPendingReviews.size(); i++) {
			TaskUser temp = allPendingReviews.get(i);
			if (projectTasks.contains(temp.task)) {
				projectPendingReviews.add(temp);
			}
		}
		if (projectPendingReviews.isEmpty())
			return null;
		return projectPendingReviews;
	}

	/**
	 * returns all tasks that has no one assigned to
	 * 
	 * @param projectID
	 *            is the project id
	 *@return List of tasks
	 */
	public List<Task> getFreeTask() {
		List<Task> tasks = this.getAllProjectTasks();
		List<Task> freeTasks = new LinkedList<Task>();
		List<TaskUserUser> TUU = new LinkedList<TaskUserUser>();
		boolean hasAss;
		for (int i = 0; i < tasks.size(); i++) {
			hasAss = false;

			TUU = tasks.get(i).getUserTaskRelation();
			for (int j = 0; j < TUU.size(); j++) {

				if (TUU.get(j).assignee != null && TUU.get(j).user == null)
					hasAss = true;

			}
			if (!hasAss)
				freeTasks.add(tasks.get(i));

		}
		return freeTasks;

	}

	/**
	 * Retrieves a list of all the requests to review a specific task type for a
	 * component within a certain project.
	 * 
	 * @param project
	 * @return List<ComponentTaskTypeUser>
	 */
	public List<ComponentTaskTypeUser> getAllComponentReviewRequests() {
		List<ComponentTaskTypeUser> componentRequests = new LinkedList<ComponentTaskTypeUser>();
		List<ComponentTaskTypeUser> allRequests = ComponentTaskTypeUser.all()
				.fetch();
		for (int i = 0; i < allRequests.size(); i++) {
			if (allRequests.get(i).component.project.equals(this)
					&& !allRequests.get(i).component.name
							.equalsIgnoreCase("default component")) {
				componentRequests.add(allRequests.get(i));
			}
		}
		return componentRequests;
	}

	/**
	 * This method gets all the unapproved requests to review a specific task
	 * type within a certain project where the request is to review the task
	 * type for the whole project.
	 * 
	 * @param project
	 *            The project for which the method retrieves the requests.
	 * @return List<ComponentTaskTypeUser> The returned list of requests
	 */
	public List<ComponentTaskTypeUser> getPendingProjectReviewRequests() {
		List<ComponentTaskTypeUser> projectRequests = new LinkedList<ComponentTaskTypeUser>();
		List<ComponentTaskTypeUser> allRequests = ComponentTaskTypeUser.all()
				.fetch();
		for (int i = 0; i < allRequests.size(); i++) {
			if (!allRequests.get(i).status
					&& this.components.contains(allRequests.get(i).component)
					&& allRequests.get(i).component.name
							.equalsIgnoreCase("default component")) {
				projectRequests.add(allRequests.get(i));
			}
		}
		return projectRequests;
	}

	/**
	 * This method gets all the unapproved requests to review a specific task
	 * type within a certain project where the request is for specific
	 * components.
	 * 
	 * @param project
	 *            The project for which the method retrieves the requests.
	 * @return List<ComponentTaskTypeUser> The returned list of requests
	 */
	public List<ComponentTaskTypeUser> getPendingComponentReviewRequests() {
		List<ComponentTaskTypeUser> componentRequests = new LinkedList<ComponentTaskTypeUser>();
		List<ComponentTaskTypeUser> allRequests = ComponentTaskTypeUser.all()
				.fetch();
		for (int i = 0; i < allRequests.size(); i++) {
			if (!allRequests.get(i).status
					&& allRequests.get(i).component.project.equals(this)
					&& !allRequests.get(i).component.name
							.equalsIgnoreCase("default component")) {
				componentRequests.add(allRequests.get(i));
			}
		}
		return componentRequests;
	}

	public List<Event> getSprints(){
		EventType sprintType = EventType.find("byType", "Sprint").first();		
		List<Event> sprints = Event.find("byProjectAndType", this,
				sprintType).fetch();		
		return sprints;		
	}
}
